package au.com.finalconcept.roo.addon.behaviours;

import java.lang.reflect.Modifier;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;

import org.springframework.roo.classpath.PhysicalTypeIdentifierNamingUtils;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.ItdTypeDetailsBuilder;
import org.springframework.roo.classpath.details.MethodMetadata;
import org.springframework.roo.classpath.details.MethodMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType;
import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.itd.AbstractItdTypeDetailsProvidingMetadataItem;
import org.springframework.roo.classpath.itd.InvocableMemberBodyBuilder;
import org.springframework.roo.metadata.MetadataIdentificationUtils;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.project.LogicalPath;
import org.springframework.uaa.client.util.Assert;

/**
 * This type produces metadata for a new ITD. It uses an
 * {@link ItdTypeDetailsBuilder} provided by
 * {@link AbstractItdTypeDetailsProvidingMetadataItem} to register a field in
 * the ITD and a new method.
 * 
 * @since 1.1.0
 */
public class RooSlugableMetadata extends AbstractItdTypeDetailsProvidingMetadataItem {
    /**
     * Get hold of a JDK Logger
     */
    private Logger log = Logger.getLogger(getClass().getName());

    private static final String PROVIDES_TYPE_STRING = RooSlugableMetadata.class.getName();
    private static final String PROVIDES_TYPE = MetadataIdentificationUtils.create(PROVIDES_TYPE_STRING);

    protected RooSlugableMetadata(String identifier, JavaType aspectName, PhysicalTypeMetadata governorPhysicalTypeMetadata) {
        super(identifier, aspectName, governorPhysicalTypeMetadata);
        Assert.isTrue(isValid(identifier), "Metadata identification string '" + identifier + "' does not appear to be a valid");

        // Adding a new sample field definition
        builder.addField(Util.getStringField(this.getId(), "slug", "slug", 3, 120, false));

        // Adding a new sample method definition
        builder.addMethod(Util.getGetterMethod(this.governorTypeDetails, String.class, this.getId(), "getSlug", "slug"));
        builder.addMethod(Util.getSetterMethod(this.governorTypeDetails, String.class, this.getId(), "setSlug", "slug"));

        if (this.governorTypeDetails.getAnnotation(new JavaType(RooTimestampable.class)) != null) {
            log.info(String.format("Type [%s] has been annotated with RooTimestampable. This is considered to be the "
                    + "master of the prePersist method. It is the job of the RooTimestampableMetadata object to create "
                    + "the prePersist method", this.governorTypeDetails.getName().getFullyQualifiedTypeName()));

        } else {

            // The RooTimestampable is not defined so we need to add
            // the prePersist methods
            builder.addMethod(getPrePersistMethod());
            
        }

        // Create a representation of the desired output ITD
        itdTypeDetails = builder.build();
    }

    private MethodMetadata getPrePersistMethod() {
        final JavaSymbolName methodName = new JavaSymbolName("prePersist");

        MethodMetadata method = Util.methodExists(governorTypeDetails, methodName, new ArrayList<AnnotatedJavaType>());
        if (method != null) {
            log
                    .warning("Found method of type prePersist on entity "
                            + governorTypeDetails.getName()
                            + " the setting of the createdAt and updatedAt may not work as expected unless you set them yourself. Please use the perPersistImpl method");
            return method;
        }

        // Define method annotations (PrePersist in this case)
        ArrayList<AnnotationMetadataBuilder> annotations;
        AnnotationMetadataBuilder annotation;
        List<AnnotatedJavaType> parameterTypes;
        List<JavaSymbolName> parameterNames;
        InvocableMemberBodyBuilder bodyBuilder;
        List<JavaType> throwsTypes;

        annotations = new ArrayList<AnnotationMetadataBuilder>();
        annotation = new AnnotationMetadataBuilder("javax.persistence.PrePersist");
        annotations.add(annotation);

        throwsTypes = new ArrayList<JavaType>();

        // Define method parameter types
        parameterTypes = new ArrayList<AnnotatedJavaType>();
        // Define method parameter names (none in this case)
        parameterNames = new ArrayList<JavaSymbolName>();

        // Create the method body
        bodyBuilder = new InvocableMemberBodyBuilder();
        RooSlugableMetadata.writePrePersistMethodBody(this.governorTypeDetails, bodyBuilder, log);
        
        // Only add the call prePersistImpl if the developer requires it
        if (governorTypeDetails.getMethod(new JavaSymbolName(Util.METHOD_NAME_PRE_PERSIST_IMPL)) != null) {
            bodyBuilder.appendFormalLine("this." + Util.METHOD_NAME_PRE_PERSIST_IMPL + "();");
        }

        // Use the MethodMetadataBuilder for easy creation of MethodMetadata
        MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(
                this.getId(),
                    Modifier.PUBLIC,
                    methodName,
                    JavaType.VOID_PRIMITIVE,
                    parameterTypes,
                    parameterNames,
                    bodyBuilder);
        methodBuilder.setAnnotations(annotations);
        methodBuilder.setThrowsTypes(throwsTypes);

        return methodBuilder.build(); // Build and return a MethodMetadata
                                      // instance
    }

    public static void writePrePersistMethodBody(ClassOrInterfaceTypeDetails governerTypeDetails, InvocableMemberBodyBuilder bodyBuilder,
            Logger log) {

        AnnotationMetadata annotationMetadata;

        annotationMetadata = governerTypeDetails.getAnnotation(new JavaType(RooSlugable.class));

        if (annotationMetadata == null) {
            log.warning(String.format("Being called by someone thinking that type [%s] is annotated with the RooSlugable annotation. "
                    + "However no such annotation is found on this type", governerTypeDetails.getName().getFullyQualifiedTypeName()));
            return;
        }

        AnnotationAttributeValue<String> fieldNamesAttributeValue;
        fieldNamesAttributeValue = annotationMetadata.getAttribute("fields");
        if (fieldNamesAttributeValue == null) {
            log.severe(String.format("Type [%s] is missing required fields attribute for the RooSlugable annotation", governerTypeDetails
                    .getName()
                        .getFullyQualifiedTypeName()));
            return;
        }

        String[] fieldNames;

        bodyBuilder.appendFormalLine("this.slug = \"\";");
        fieldNames = fieldNamesAttributeValue.getValue().split(",");
        int counter = 0;
        bodyBuilder.appendFormalLine("String slugableCurrentSegment;");
        for (String fieldName : fieldNames) {
            String line;

            bodyBuilder.appendFormalLine(String.format(
                    "slugableCurrentSegment = (this.%s == null ? \"null\" : this.%s.toString());",
                        fieldName,
                        fieldName));
            line = String.format(
                    "this.slug += \"%s\" + slugableCurrentSegment.trim().replace(' ', '-').toLowerCase();",
                        (counter++ > 0 ? "-" : ""),
                        fieldName);

            bodyBuilder.appendFormalLine(line);

        }

    }

    public String toString() {
        final StringBuilder builder = new StringBuilder();
        builder.append("identifier: " + getId().toString());
        builder.append("valid: " + valid);
        builder.append("aspectName: " + aspectName);
        builder.append("destinationType: " + destination);
        builder.append("governor: " + governorPhysicalTypeMetadata.getId());
        builder.append("itdTypeDetails: "+ itdTypeDetails);
        return builder.toString();
    }

    public static boolean isValid(String metadataIdentificationString) {
        return PhysicalTypeIdentifierNamingUtils.isValid(PROVIDES_TYPE_STRING, metadataIdentificationString);
    }

    public static final String getMetadataIdentiferType() {
        return PROVIDES_TYPE;
    }

    public static final String createIdentifier(JavaType javaType, LogicalPath path) {
        return PhysicalTypeIdentifierNamingUtils.createIdentifier(PROVIDES_TYPE_STRING, javaType, path);
    }

    public static final JavaType getJavaType(String metadataIdentificationString) {
        return PhysicalTypeIdentifierNamingUtils.getJavaType(PROVIDES_TYPE_STRING, metadataIdentificationString);
    }

    public static final LogicalPath getPath(String metadataIdentificationString) {
        return PhysicalTypeIdentifierNamingUtils.getPath(PROVIDES_TYPE_STRING, metadataIdentificationString);
    }
}
